use axum::Json;
use axum::extract::{ConnectInfo, OriginalUri, State};
use axum::http::StatusCode;
use axum::response::IntoResponse;
use my_rust_core::constants::{self, server_code};
use serde::Deserialize;
use serde_json::{Value, json};
use std::net::SocketAddr;
use std::sync::Arc;

use crate::AppService;

pub async fn index() -> impl IntoResponse {
    super::shared::get_data_response(json!({
      "name": "authentication"
    }))
}

#[derive(Debug, Deserialize)]
pub struct RegisterParam {
    username: String,
    password: String,

    #[serde(rename = "fullName")]
    full_name: String,
}

/// https://docs.rs/axum/latest/axum/extract/index.html#the-order-of-extractors
pub async fn register(
    service: State<Arc<AppService>>,
    OriginalUri(original_uri): OriginalUri,
    Json(param): Json<RegisterParam>,
) -> impl IntoResponse {
    // Why this action is quite slow?
    // Because it involves Argon2 which is highly time-consuming but wins on security.
    let result = service
        .account_group_service
        .register(&param.username, &param.password, &param.full_name)
        .await;

    if result.is_err() {
        return super::shared::get_error_response(
            StatusCode::UNAUTHORIZED,
            server_code::UNAUTHORIZED,
            constants::error::ERROR_USER_ACCOUNT_EXISTS,
            original_uri.path(),
        );
    }

    (StatusCode::OK, Json(json!({})))
}

#[derive(Debug, Deserialize)]
pub struct LoginParam {
    username: String,
    password: String,

    #[serde(rename = "deviceInfo")]
    device_info: Value,

    #[serde(rename = "systemInfo")]
    system_info: Value,
}

/// https://docs.rs/axum/latest/axum/extract/index.html#the-order-of-extractors
pub async fn login(
    service: State<Arc<AppService>>,
    ConnectInfo(addr): ConnectInfo<SocketAddr>,
    OriginalUri(original_uri): OriginalUri,
    Json(param): Json<LoginParam>,
) -> impl IntoResponse {
    // Why this action is quite slow?
    // Because it involves Argon2 which is highly time consuming but wins on security.
    let (my_user_details, error_code) = service
        .authentication_service
        .authenticate_by_credential(
            &param.username,
            &param.password,
            addr.ip().to_string().as_str(),
            &param.device_info,
            &param.system_info,
        )
        .await;

    // All response data format is for the client which works with spring security rest.
    if error_code.is_empty() {
        return super::shared::get_data_response(serde_json::to_value(&my_user_details).unwrap());
    } else {
        super::shared::get_error_response(
            StatusCode::UNAUTHORIZED,
            server_code::UNAUTHORIZED,
            error_code.as_str(),
            original_uri.path(),
        )
    }
}

#[derive(Debug, Deserialize)]
pub struct RefreshTokenParam {
    #[serde(rename = "refreshToken")]
    refresh_token: String,

    #[serde(rename = "deviceInfo")]
    device_info: Value,

    #[serde(rename = "systemInfo")]
    system_info: Value,
}

/// https://docs.rs/axum/latest/axum/extract/index.html#the-order-of-extractors
pub async fn refresh_token(
    service: State<Arc<AppService>>,
    ConnectInfo(addr): ConnectInfo<SocketAddr>,
    OriginalUri(original_uri): OriginalUri,
    Json(param): Json<RefreshTokenParam>,
) -> impl IntoResponse {
    let (my_user_details, error_code) = service
        .authentication_service
        .refresh_token(
            &param.refresh_token,
            &addr.ip().to_string(),
            &param.device_info,
            &param.system_info,
        )
        .await;

    // All response data format is for the client which works with spring security rest.
    if error_code.is_empty() {
        return super::shared::get_data_response(serde_json::to_value(&my_user_details).unwrap());
    } else {
        super::shared::get_error_response(
            StatusCode::UNAUTHORIZED,
            server_code::UNAUTHORIZED,
            error_code.as_str(),
            original_uri.path(),
        )
    }
}
